By default Kademi gives you a signup page for each user Group which is open or has administrator review. But often you will want to make your own registration form:
- to have a single form which allows selecting the group
- to optimise screen layout
- implement custom validation logic, etc
Which file to edit?
If you want to stick with the default signup page per group, then you can override the default registration page template.
But you can also create a new html page at any location. As long as the form posts to the correct location (using ajax) the result is the same.
Which URL to POST to, ie what form action to use
The registration form must be posted to the url for the group the user is registering to, eg /signup/Learners or /signup/Staff, etc. You can find these URLs from the manage groups page, from the "Signup page" button:
Choosing an organisation
You may want your users to select an organisation unit when registering. For example, if your users are retail staff and you have a database of stores, you might want them to select a store. That list might be short, so you can just use a select box, or you might have thousands of stores so you need users to search for their organisation.
Kademi supports both options. You can list organisations with a #foreach loop to generate select options, or you can use twitter type ahead
The form html
Here is some example template html code, an explanation is below:
<form method="post" id="registerForm" action="$page.name" class="form-horizontal"> <fieldset> <legend>Create a new account</legend> <div class="control-group form-group"> <label class="control-label col-md-3" for="email">Email</label> <div class="controls col-md-9"> <input class="required form-control" required="true" type="email" name="email" id="email" placeholder="Your email address" /> </div> </div> <div class="control-group form-group"> <label class="control-label col-md-3" for="nickName">Nickname</label> <div class="controls col-md-9"> <input class="required form-control" type="text" name="nickName" id="nickName" placeholder="Your nickname for others to see" /> </div> </div> <div class="control-group form-group"> <label class="control-label col-md-3" for="password">Password</label> <div class="controls col-md-9"> <input class="required form-control" type="password" name="password" id="password" placeholder="Your preferred password" /> </div> </div> <div class="control-group form-group"> <label class="control-label col-md-3" for="confirmPassword">Confirm</label> <div class="controls col-md-9"> <input class="required form-control" type="password" name="confirmPassword" id="confirmPassword" placeholder="Confirm your password" /> </div> </div> #foreach($field in $page.extraFields) <div class="control-group form-group"> <label class="control-label col-md-3" for="">$field.text</label> <div class="controls col-md-9"> $field.html </div> </div> #end #if( $page.hasOrgs ) #if( $groupRegoPage.regoOrgType ) #set($orgType = $groupRegoPage.regoOrgType) #else #set($orgType = "Organisation") #end <div class="control-group form-group"> <label class="control-label col-md-3" for="orgName">$orgType</label> <div class="controls col-md-9"> <input type="hidden" name="orgId" id="orgId" /> <input class="required form-control" type="text" name="orgName" id="orgName" autocomplete="off" placeholder="Choose your $orgType" /> </div> </div> #end #if( $page.hasOptins() ) <div class="control-group form-group"> <div class="controls col-md-9 col-md-offset-3"> #foreach($optin in $page.optins) <label for="optins_${optin.optinGroup.name}"> <input type="checkbox" name="optins" value="$optin.optinGroup.name" id="optins_${optin.optinGroup.name}" /> $optin.message </label> #end </div> </div> #end <div class="control-group form-group"> <div class="controls col-md-9 col-md-offset-3" > <button type="submit" class="btn btn-primary">Register</button> <a href="/index.html" class="btn">Cancel</a> </div> </div> </fieldset> </form>
And the corresponding javascript:
$(document).ready(function() { initRegister("/dashboard"); }); function initRegister(afterRegisterHref) { log("init labels") var form = $("#registerForm"); var lastTabIndex = 0; jQuery("#registerForm label.collapse").each(function(i, n) { var label = jQuery(n); var title = label.text(); var input = form.find("#" + label.attr("for")); input.attr("title", title); input.attr("tabindex", i + 1); log("set tab index", input, i + 1); label.text(i + 1); lastTabIndex = i + 2; }); form.find("button[type=submit]").attr("tabindex", lastTabIndex); initRegisterForms(afterRegisterHref); } function initRegisterForms(afterRegisterHref, callback) { log("initRegisterForms - bootstrap300", jQuery("#registerForm")); $("#registerForm").forms({ validationFailedMessage: "Please enter your details below.", callback: function(resp, form) { if (resp.messages && resp.messages[0] == "pending") { showPendingMessage(); } else { log("created account ok, logging in...", resp, form); var userName = form.find("input[name=email]").val(); var password = form.find("input[name=password]").val(); doLogin(userName, password, { afterLoginUrl: afterRegisterHref, urlSuffix: "/.dologin", loginCallback: callback }, this); } } }); $.getScript("/static/typeahead/0.10.2/typeahead.bundle.js", function() { $.getScript("/static/handlebars/1.2.0/handlebars.js", function() { try { var searchUrl = $("#registerForm").attr("action"); var orgs = new Bloodhound({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), queryTokenizer: Bloodhound.tokenizers.whitespace, remote: { url: searchUrl + '?jsonQuery=%QUERY&th', replace: function() { return $("#registerForm").attr("action") + '?jsonQuery=' + encodeURIComponent($("#orgName").val()) + '&th'; } } }); orgs.initialize(); $("#orgName").typeahead(null, { minLength: 1, valueKey: "title", name: "orgs", source: orgs.ttAdapter(), templates: { empty: [ '<div class="empty-message">', 'No organisations match your search', '</div>' ].join('\n'), suggestion: Handlebars.compile('<p><strong>{{title}}</strong> {{address}}, {{postcode}}</p>') } }); $("#orgName").on("typeahead:selected", function(e, datum) { log("Selected", e, datum); $("#orgId").val(datum.orgId); }); flog("init typeahead3"); } catch (e) { flog("exception: " + e); } }); }); function showPendingMessage() { showModal($("#pending")); } }
Identifying the group through the form action
A registration form always creates a registration to a specified user group. You specify the group by setting the form action attribute, where the action is the group name followed by '/signup'. Eg for a group called "retail-staff" the form would be like this:
<form method="post" id="registerForm" action="/retail-staff/signup" class="form-horizontal">
This causes the form to be POST'ed to the url /retail-staff/signup. Also, any organisation lookup queries are sent to that url and only options valid for that group are returned.
Form ajax handling with jquery.forms
The server side handler is designed for use with ajax submissions, because it returns json data indicating status and any validation errors. So you must use ajax to POST.
You can submit the ajax POST request by any means, but the default and recommended approach is to use an inline form and initialise it with the Kademi jquery.forms plugin. This will intercept the submit event, apply validation rules and, if valid, serialise form inputs and send them to the server in an ajax request. Any validation errors in the response are mapped to the inputs in the form and displayed appropriately.
Initialising the forms plugin is simple:
$("#registerForm").forms({ // callback and other options go here })
The plugin has options for functions which
- add additional validation prior to submit
- successful registration,
- handling errors
Form inputs
The registration form has certain inputs which have special meaning and must have specified input names
- email - the email address to register with
- nickName - the display name for this user, ie visible to other users in comments etc
- password - the password to create. If a confirmPassword input is present the default form handling will verify they match, but its not required for server processing
- orgId - the unique ID of the organisation to register to. If empty defaults to your account organisation. This is normally a hidden input because users will search for their company by typing in its name and/or address. The default form uses twitter typeahead to show search results as the user types. You can also use a select box which simply lists all available organisations, and in some cases it will make sense to require your users to enter a code for their organisation.
Additionall you can have inputs for any custom fields defined by the group
Organisation search
You can get a list of organsiations as JSON by sending a GET request to the group signup url (see section above) with the following parameters:
- jsonQuery - this is the value to search for, and indicates results must be in JSON
- th - return results optimised for twitter typeahead. The parameter has no value.
- where-X - apply a field, only returning organisations where the field X has the given value
Example - find organisations matching 'cal', which have a brand field of Petbarn, and return results optimised for twitter typeahead
http://www.pet-ed.com.au/ps/signup?jsonQuery=cal&where-brand=Petbarn&th
Which returns this:
[ { "address": "2 Sydal Street", "postcode": "4551", "addressState": "Queensland", "title": "Petbarn - Caloundra", "hasPrimaryMembers": true, "orgId": "0019000000KoWcQAAV", "phone": "07 31813203", "addressLine2": "CALOUNDRA", "tokens": [], "id": 19567350, "state": "Queensland", "fields": { "brand": "Petbarn"}, "value": "0019000000KoWcQAAV" }, { "address": "Cnr Georgia Cres & Callanan Rd", "postcode": "830", "addressState": "Northern Territory", "title": "Petbarn - Yarrawonga.", "hasPrimaryMembers": true, "orgId": "0019000000KoNCGAA3", "phone": "08 8931 1085", "addressLine2": "Yarrawonga", "tokens": [], "id": 19570301, "state": "Northern Territory", "fields": {"brand": "Petbarn"}, "value": "0019000000KoNCGAA3" } ]
Using opt-in groups
Often you will want your users to be able to choose to register to additional groups, ie to "opt-in" to mailing lists.
To do that:
- Create a group for the opt-in in the Manage Groups page, ie "promotions"
- Go to the settings for the primary group, and add that group as an opt-in
- On the registration form, add a checkbox with name="optins" with value="promotions" (or whatever you called your optin group)
Ask a question, or offer an answer